package ferp.center.server.backend;

import ferp.center.server.Action;
import ferp.center.server.entity.Bid;
import ferp.center.server.entity.Config;
import ferp.center.server.servlet.ConfigGet;
import ferp.core.ai.nn.Adaptation;
import ferp.core.ai.nn.nen.Nen;

import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;

/**
 * User: igorgok
 * Date: 12/6/12 3:49 PM
 */
public class BidTrain extends HttpServlet
{
  // queue task for the "training" backend
  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
  {
    // create "train bid NN" task for the backend
    Action.queueTaskForBackend(response, "bid NN training", "/bid/train");
  }

  // the following will run out of the "push" task queue in a "training" backend
  protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
  {
    Action.execute(request, response, Void.class, new Action.Read<Void, Void>()
    {
      @SuppressWarnings("unchecked")
      public Void run(EntityManager em, Void input) throws IOException
      {
        long start = System.currentTimeMillis();
        Query q = em.createQuery("SELECT b FROM Bid b");
        List<Bid> bids = q.getResultList();

        if (bids.size() > MINIMUM_BIDS)
        {
          // NN input and output sets
          List<float[]> is = new LinkedList<float[]>(), os = new LinkedList<float[]>();

          // create input and output sets for training
          // we can expand one bid entity to serve 24 actual positions by permutating suits in hand
          for (Bid b : bids)
            Adaptation.bid2nnt(is, os, b.getCards(), b.getHand(), b.getStartingTricks(), b.getLast(), b.getPenalty(), b.getResult());

          // create arrays
          float[][] ia = new float[is.size()][], oa = new float[is.size()][];
          int i = 0, j = 0;

          for (float[] fa : is)
            ia[i++] = fa;

          for (float[] fa : os)
            oa[j++] = fa;

          long prepared = System.currentTimeMillis();
          int hls = Nen.guessHiddenCLS(ia.length, ia[0].length, oa.length);

          // create the network
          Nen nen = Adaptation.createBidNN(hls);

          // train
          nen.train(ia, oa, true, "bids", null, null, 2);

          long trained = System.currentTimeMillis();

          // test
          float[][] prediction = nen.get(ia);

          i = 0;
          for (int n = 0; n < prediction.length; ++n)
          {
            float r = (prediction[n][0] <= 0 ? -1 : 1);

            if (oa[n][0] == r)
              ++i;

            //log("[" + oa[n][0] + "] <--> [" + r + "]");
          }

          float error = (1 - (float) i / (float) prediction.length);

          long tested = System.currentTimeMillis();

          // store
          long time = (trained - prepared) / 1000;
          Config old = ConfigGet.current(em);
          Config cfg = (old == null) ? new Config(nen, is.size(), hls, error, time) :
                                       new Config(old, nen, is.size(), hls, error, time);

          em.persist(cfg);

          long serialized = System.currentTimeMillis();

          log("Training set size: " + prediction.length);
          log("Guessed hidden layer size: " + hls);
          log("Error on the same training set: " + (error * 100) + "%");
          log("Saved network state size: json=" + cfg.getBidNNJson().length()); // + ", serialized=" + cfg.getNnb().length);
          log("Times: prepare=" + (prepared - start) + ", train=" + (trained - prepared) +
              ", serialize=" + (serialized - tested) + ", test=" + (tested - trained) + " ms");
        }
        else
          log("not enough bids for training: " + bids.size());

        return null;
      }
    });
  }

  private static final int MINIMUM_BIDS = 10000;

/*
  protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
  {
    Action.execute(request, response, Void.class, new Action.Read<Void, Void>()
    {
      @SuppressWarnings("unchecked")
      public Void run(final EntityManager em, Void input) throws IOException
      {
        ThreadFactory tf = ThreadManager.currentRequestThreadFactory();

        tf.newThread(new Runnable()
        {
          @Override
          public void run()
          {
            long start = System.currentTimeMillis();
            Query q = em.createQuery("SELECT b FROM Bid b");

            log("Started training thread");

            List<Bid> bids = q.getResultList();

            if (bids.size() > 0)
            {
              // NN input and output sets
              List<float[]> is = new LinkedList<float[]>(), os = new LinkedList<float[]>();

              // create input and output sets for training
              // we can expand one bid entity to serve 24 actual positions by permutating suits in hand
              for (Bid b : bids)
                Adaptation.bid2nnt(is, os, b.getCards(), b.getHand(), b.getStartingTricks(), b.getLast(), b.getPenalty(), b.getResult());

              // create arrays
              float[][] ia = new float[is.size()][], oa = new float[is.size()][];
              int i = 0, j = 0;

              for (float[] fa : is)
                ia[i++] = fa;

              for (float[] fa : os)
                oa[j++] = fa;

              long prepared = System.currentTimeMillis();
              int hls = Nen.guessHiddenCLS(ia.length, ia[0].length, oa.length);

              // create the network
              Nen nen = Adaptation.nen(hls);

              // train
              nen.train(ia, oa, false, "bids", null, null, 2);

              long trained = System.currentTimeMillis();

              // test
              float[][] prediction = nen.get(ia);

              i = 0;
              for (int n = 0; n < prediction.length; ++n)
              {
                float r = (prediction[n][0] <= 0 ? -1 : 1);

                if (oa[n][0] == r)
                  ++i;

                //log("[" + oa[n][0] + "] <--> [" + r + "]");
              }

              float error = (1 - (float) i / (float) prediction.length);

              long tested = System.currentTimeMillis();

              // store
              try
              {
                Config cfg = new Config(nen, is.size(), hls, error, (trained - prepared) / 1000);

                em.persist(cfg);

                long serialized = System.currentTimeMillis();

                log("Training set size: " + prediction.length);
                log("Guessed hidden layer size: " + hls);
                log("Error on the same training set: " + (error * 100) + "%");
                log("Saved network state size: json=" + cfg.getNnj().length()); // + ", serialized=" + cfg.getNnb().length);
                log("Times: prepare=" + (prepared - start) + ", train=" + (trained - prepared) +
                    ", serialize=" + (serialized - tested) + ", test=" + (tested - trained) + " ms");
              }
              catch (IOException e)
              {
                System.out.println(e.getMessage());
              }
            }
          }
        }).start();

        return null;
      }
    });
  }
*/
}
